#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <setjmp.h>

#include "include.h"
#include "HelperUtils.h"
#include "ClientHandler.h"

#include "ServerMain.h"

volatile int sigtermReceived;
sigjmp_buf environment;

void sigtermHandler();

int serverMain(int serverPort, char* documentRoot) {
    // Setup a SIGTERM handler
    sigtermReceived = 0;
    struct sigaction newAction;
    newAction.sa_handler = sigtermHandler;
    sigemptyset(&newAction.sa_mask);
    newAction.sa_flags = 0;
    if ( sigaction (SIGTERM, &newAction, NULL) == -1 ) {
        dieWithError("FATAL: sigaction() failed.");
    }
    
    // Setup a SIGTERM mask
    sigset_t sigtermMask;
    sigemptyset(&sigtermMask);
    sigaddset(&sigtermMask, SIGTERM);

    // Block all SIGTERM until initialization is finished
    sigprocmask(SIG_BLOCK, &sigtermMask, NULL);

    // General for loop counter
    int i;  
    // For socket
    int     serverSocket,
            clientSocket;
    struct sockaddr_in  serverAddress,
                        clientAddress;

    // socket(), bind(), listen()
    if ((serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        dieWithError("FATAL: socket() failed");
    memset(&serverAddress, 0, sizeof(serverAddress));
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(serverPort);

    if (bind(serverSocket, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0)
        dieWithError("FATAL: bind() failed");
    if (listen(serverSocket, MAXPENDING) < 0)
        dieWithError("FATAL: listen() failed");
    #if INFO == 1
    fprintf(stdout, "INFO: Listening on port %d for incoming connection.\n", serverPort);
    #endif
    
    // For forking children
    int clientAddressLength;
    pid_t clientHandlerPid;
    int clientReturnValue;
    int clientCount = 0;

    // Keep track of all pid of all children
    pid_t clientPidTable[MAXCLIENT];
    // Initialize clientPidTable
    for (i=0; i<MAXCLIENT; i++) {
        clientPidTable[i] = 0;
    }

    // Accpet connection and fork childs
    while (1) {
        // Return point from sigtermHandler
        sigsetjmp(environment, 1);
        // Handles SIGTERM
        if (sigtermReceived == 1) {
            // Send sigterm to all children
            for (i=0; i<MAXCLIENT; i++) {
                if (clientPidTable[i]!=0) {
                    kill(clientPidTable[i], SIGTERM);
                }
            }
            // Wait for all children to terminate
            while( wait(NULL) != -1 );
            // Close server socket
            close(serverSocket);
            // Return
            return 0;
        }

        // Setup is finished, unblock SIGTERM only when blocking on accept()
        sigprocmask (SIG_UNBLOCK, &sigtermMask, NULL); 

        // Accept client connection
        clientAddressLength = sizeof(clientAddress);
        if ((clientSocket = accept(serverSocket, (struct sockaddr *) &clientAddress, (socklen_t *)&clientAddressLength)) < 0)
            dieWithError("FATAL: accpet() failed.");
        #if INFO == 1
        fprintf(stdout, "INFO: Handling client %s\n", inet_ntoa(clientAddress.sin_addr));
        #endif

        // Block SIGTERM until updating clientPidTable is done
        sigprocmask (SIG_UNBLOCK, &sigtermMask, NULL); 

        // fork child to handle the connection
        switch( clientHandlerPid = fork() ) {
            case -1:
                // Error
                dieWithError("FATAL: fork() failed.");
            case 0:
                // Call client handler
                clientReturnValue = handleClient(clientSocket, clientAddress, documentRoot);
                exit(clientReturnValue);
            default:
                // Increase client count
                clientCount++;

                // Update clientPidTable
                for (i=0; i<MAXCLIENT; i++) {
                    if (clientPidTable[i]==0) {
                        clientPidTable[i] = clientHandlerPid;
                        break;
                    }
                }

                // Once clientPidTable update is finished, unblock SIGTERM
                sigprocmask (SIG_UNBLOCK, &sigtermMask, NULL); 
                
                // If max client count is reached, wait for one to finish
                if (clientCount == MAXCLIENT) {
                    #if DEBUG == 1
                    fprintf(stderr, "DEBUG: Maximum client count reached, new connections will be blocked.\n");
                    #endif
                    
                    // Wait for one client to finish
                    clientHandlerPid = wait(NULL);
                    // Decrease client count
                    clientCount--;
                    // Update clientPidTable
                    for (i=0; i<MAXCLIENT; i++) {
                        if (clientPidTable[i]==clientHandlerPid) {
                            clientPidTable[i] = 0;
                            break;
                        }
                    }

                    #if DEBUG == 1
                    fprintf(stderr, "DEBUG: Process pool freeed, new connections will be accepted.\n");
                    #endif
                }
                break;
        }
    }
    return 0;
}

void sigtermHandler() {
    sigtermReceived = 1;
    siglongjmp(environment, 1);
}

